/*
 * aki-usb.c - Controling Akizuki 32x16 LED Matrix
 *
 * USB version
 *
 *
 * Copyright (C) 2009  Free Software Initiative of Japan
 *
 * Written by Niibe Yutaka
 *
 * Enhanced by Wiktor Langowski <jolkaczad@gmail.com>
 *  - more control over display parameters
 *  - controlling the parameters from host side "live"
 *  - utilising EEPROM for keeping text/bitmap and settings between boots
 *  - cleaning up
 *
 * Distributed under GNU General Public Licence version 3 (or later)
 */

/*
 * You need V-USB to build the target executable.
 * See http://www.obdev.at/vusb/
 */

/*
 * $ avr-gcc -Wall -O2 -c -mmcu=atmega168 aki-usb.c
 * $ avr-gcc -mmcu=atmega168 usbdrv.o usbdrvasm.o aki-usb.o -o aki-usb.elf
 * $ avr-objcopy -j .text -j .data -O ihex aki.elf aki.hex
 * $ sudo avrdude -p m168 -c usbasp -U flash:w:aki.hex:i
 */

#ifndef F_CPU 
/* Your CPU clock */
#define F_CPU 20000000
#endif

#include <setjmp.h>
#include <string.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <util/delay.h>
#include <avr/eeprom.h>

#include "usbdrv/usbdrv.h"
#include "font.h"


#define __STRINGIFY(x) # x
#define STRINGIFY(x) __STRINGIFY(x)

/*
 * Sleep and wait for interrupts
 */
static void
sys_sleep (void)
{
  set_sleep_mode (SLEEP_MODE_IDLE);
  sleep_enable ();
  asm volatile ("sei");
  sleep_cpu ();
  sleep_disable ();
}

/*
 * Initialize the wave generation of OC1A for LAT and OC1B for STR
 * and the timer interrupts
 *
 * LAT is asserted at every 1 msec for 2.4 usec (inverted).
 * STR is oscillated at 1kHz and duty ratio means the brightness.
 *
 * OC1A generates an interrupt at every 1 msec.
 */
#define TIMER_TOP (2500 - 1)	/* 1 msec = (2500 * 8) / 20MHz  */
static void
sys_timer_init (void)
{
  cli ();
  TIMSK1 = 0x02;		/* Enable oc-match-1a interrupt */
  TCCR1B = 0x18;		/* Fast PWM mode with ICR1, Stop timer */
  TCNT1  =    0;
  TCCR1A = 0xb2;		/* Fast PWM mode, non-inverting A, inverting B, ICR1=TOP */
  ICR1   =  TIMER_TOP;		/* Every 1 msec */
  OCR1A  =  TIMER_TOP - 6;	/* Latch = 2.4 usec */
  OCR1B  =  0;			/* Duty ratio of OC1B means the brightness */
  TCCR1B = 0x1a;		/* Fast PWM mode with ICR1, Start timer with 1/8 pre-scaler */
  sei ();
}

/*
 * This is a glue code for interrupt handler.
 */
asm (".global	" STRINGIFY(TIMER1_COMPA_vect) "\n"
"	.type	" STRINGIFY(TIMER1_COMPA_vect) ", @function\n"
"	.global	sys_timer_intr\n"
"	.type	sys_timer_intr, @function\n"
"sys_timer_intr:\n"
STRINGIFY(TIMER1_COMPA_vect) ":\n"
"	sei\n"
"	push	r31\n"
"	in	r31,__SREG__\n"
"	push	r31\n"
"	push	r30\n"
"	push	r27\n"
"	push	r26\n"
"	push	r25\n"
"	push	r24\n"
"	push	r23\n"
"	push	r22\n"
"	push	r21\n"
"	push	r20\n"
"	push	r19\n"
"	push	r18\n"
"	push	r1\n"
"	push	r0\n"
"	clr	r1\n"
"	rcall	timer_intr\n"
"	pop	r0\n"
"	pop	r1\n"
"	pop	r18\n"
"	pop	r19\n"
"	pop	r20\n"
"	pop	r21\n"
"	pop	r22\n"
"	pop	r23\n"
"	pop	r24\n"
"	pop	r25\n"
"	pop	r26\n"
"	pop	r27\n"
"	pop	r30\n"
"	pop	r31\n"
"	out	__SREG__,r31\n"
"	pop	r31\n"
"	reti\n"
"	.size	sys_timer_intr, .-sys_timer_intr"
);

#define SIGNAL_LED	_BV(PORTC5)
#define SIGNAL_CLK	_BV(PORTC2)
#define SIGNAL_COL	_BV(PORTC1)
#define SIGNAL_ROW	_BV(PORTC0)

#define PORTC_OTHER_MASK (SIGNAL_LED)

/*
 * io_init -- initialize I/O port(s)
 *
 * Port C 0..2 and 5 are output ports for LED and CLE, COL, and ROW.
 * Port B 1..2 are output ports for LAT and STR.
 */
static void io_init (void)
{
  DDRC |= SIGNAL_LED | SIGNAL_CLK | SIGNAL_COL | SIGNAL_ROW;
  PORTC = SIGNAL_LED;
  DDRB |= _BV(PORTB2) | _BV(PORTB1); /* OC1A and OC1B */
}

/*
 * Set brightness of the display
 */
#define BRIGHTNESS_MAX 12 /* Display has brightness value: 0..12 (12 is the brightest) */
static unsigned short b_value[] = { 0, 1, 2, 4, 9, 19, 39, 78, 156, 312, 625, 1250, 2500 };
static void set_brightness (unsigned char brightness)
{
  unsigned short b;
  if (brightness > BRIGHTNESS_MAX)
    brightness = BRIGHTNESS_MAX;
  b = b_value[brightness];

  cli ();
  OCR1B  = b;
  sei ();
}

/*
 * Video mode
 */
#define VMODE_STATIC   0
#define VMODE_SAWTOOTH 1 /* Sawtooth wave */
#define VMODE_TRIANGLE 2 /* Triangle wave (up) */
#define VMODE_TRIANGLE_DOWN 3 /* Triangle wave (down) */
#define VMODE_BLINK    4
static unsigned char vmode = VMODE_TRIANGLE;

/*
 * Video ram position
 */
static unsigned char vram_disp_pos = 0;

/*
 * Video ram
 */
#define DISP_ROWS	16
#define DISP_COLUMNS	32
#define VRAM_MAX (DISP_COLUMNS*DISP_ROWS/8*2) /* Power of two */
static unsigned char vram[VRAM_MAX];

#define COUNT_MAX 16

static int line = 0;

/*
 * timer_intr - the interrupt handler
 *
 * This display works by interrupt driven programming model.
 * Every 1msec, this routine is called with other interrupts enabled.
 * USB transfers may interrupts timer_intr.
 *
 * This routine let the display emit light for a single line.
 */
static unsigned char count_max;
__attribute__((used)) /* Inform GCC that this routine is actually used. */
static void timer_intr (void)
{
  static unsigned int cnt = 0;
  static unsigned char b = 0;
  int col;

  for (col = DISP_COLUMNS-1; col >= 0; col--)
    {
      unsigned char signal_row;
      unsigned char signal_col;
      unsigned char v;

      v = PORTC;
      signal_row = (line == (~col & (DISP_ROWS - 1)))?SIGNAL_ROW:0;
      if ((vram[((vram_disp_pos + col)*2+(line>=8?0:1))&(VRAM_MAX - 1)]
	   & (1 << (line&0x7))))
	signal_col = SIGNAL_COL;
      else
	signal_col = 0;

      PORTC = signal_row | signal_col | (v&PORTC_OTHER_MASK);

      _delay_us (2);

      v = PORTC;
      PORTC = SIGNAL_CLK | signal_row | signal_col | (v&PORTC_OTHER_MASK);

      _delay_us (1);
    }

  if (++line >= DISP_ROWS)
    {
      line = 0;
      cnt++;
      if (cnt > count_max)
	{
	  PORTC ^= SIGNAL_LED;
	  cnt = 0;

	  if ((vmode))
	    {
	      if (vmode == VMODE_TRIANGLE_DOWN)
		{
		  if (--b == 0)
		    vmode = VMODE_TRIANGLE;
		}
	      else if (vmode == VMODE_BLINK)
		b = (b == 0) ? BRIGHTNESS_MAX : 0;
	      else
		b++;

	      if (b > BRIGHTNESS_MAX)
		{
		  if (vmode == VMODE_TRIANGLE)
		    vmode = VMODE_TRIANGLE_DOWN;
		  else
		    b = 0;
		}

	      set_brightness (b);
	    }
	}
    }
}

/*
 * usb_init - USB initialization
 *
 * Call V-USB's usbInit, and wait for configuration will be done.
 */
static unsigned char in_usb_init;

static void
usb_init (void)
{
  uint16_t i;

  in_usb_init = 1;
  usbConfiguration = 0;

  usbInit ();

  usbDeviceDisconnect();
  _delay_ms (500);
  usbDeviceConnect();

  /*
   * this loop serves as timeout for detecting a USB connection
   */
  for (i = 0; i < (1 << 10) ; i++)
    {
      sys_sleep ();
      usbPoll ();
      asm ("" : : : "memory");
      if (usbConfiguration == 1)
	break;
    }

  PORTC ^= SIGNAL_LED;
  in_usb_init = 0;
}

/*
 * USB descriptor
 *
 */
PROGMEM char usbDescriptorConfiguration[] = {    /* USB configuration descriptor */
    9,          /* sizeof(usbDescriptorConfiguration): length of descriptor in bytes */
    USBDESCR_CONFIG,    /* descriptor type */
    USB_CONFIG_LENGTH, 0,
                /* total length of data returned (including inlined descriptors) */
    1,          /* number of interfaces in this configuration */
    1,          /* index of this configuration */
    0,          /* configuration name string index */
#if USB_CFG_IS_SELF_POWERED
    USBATTR_SELFPOWER,      /* attributes */
#else
    (char)USBATTR_BUSPOWER, /* attributes */
#endif
    USB_CFG_MAX_BUS_POWER/2,            /* max USB current in 2mA units */
/* interface descriptor follows inline: */
    9,          /* sizeof(usbDescrInterface): length of descriptor in bytes */
    USBDESCR_INTERFACE, /* descriptor type */
    0,          /* index of this interface */
    0,          /* alternate setting for this interface */
    1 + USB_CFG_HAVE_INTRIN_ENDPOINT, /* endpoints excl 0: number of endpoint descriptors to follow */
    USB_CFG_INTERFACE_CLASS,
    USB_CFG_INTERFACE_SUBCLASS,
    USB_CFG_INTERFACE_PROTOCOL,
    0,          /* string index for interface */

#if USB_CFG_HAVE_INTRIN_ENDPOINT    /* endpoint descriptor for endpoint 1 */
    7,          /* sizeof(usbDescrEndpoint) */
    USBDESCR_ENDPOINT,  /* descriptor type = endpoint */
    (char)0x81, /* IN endpoint number 1 */
    0x03,       /* attrib: Interrupt endpoint */
    8, 0,       /* maximum packet size */
    USB_CFG_INTR_POLL_INTERVAL, /* in ms */
#endif
#if 1   /* endpoint descriptor for endpoint 2 */
    7,          /* sizeof(usbDescrEndpoint) */
    USBDESCR_ENDPOINT,  /* descriptor type = endpoint */
    (char)0x02, /* OUT endpoint number 1 */
    0x03,       /* attrib: Interrupt endpoint */
    8, 0,       /* maximum packet size */
    USB_CFG_INTR_POLL_INTERVAL, /* in ms */
#endif
};

/*
 * Callbacks are defined by V-USB
 *  usbFunctionSetup: control transfer
 *  usbFunctionWriteOut: interrupt write transfer
 */

/*
 * XXX
 * The value depends on which ATmegaxx8 we're using. This should be moved
 * to a global header file.
 */
#define EEPROM_SIZE 512
#define EEPROM_SASIZE 7 /* eeprom settings array size */

/*
 * We use control transfer when a command requires response
 * from the device.
 */
#define CMD_GET_SETTINGS_EEPROM 1

usbMsgLen_t
usbFunctionSetup(uchar data[8])
{
  usbRequest_t *rq = (void *)data;
  static uchar replyBuf[8];

  usbMsgPtr = replyBuf;
  switch (rq -> bRequest)
    {
    case CMD_GET_SETTINGS_EEPROM:
      eeprom_read_block (replyBuf, (const void *)EEPROM_SIZE - EEPROM_SASIZE, \
			 EEPROM_SASIZE);
      return EEPROM_SASIZE;
      break;
    }
  return 0;
}

static uchar cmd_len;
static uchar cmd_data[8];
#define CMD_DATA   0
#define CMD_SETPTR 1		/* deprecated */
#define CMD_CLRSCR 2
#define CMD_VMODE  3
#define CMD_CHAR   4
#define CMD_SETDSP 5
#define CMD_SETCOL 6
#define CMD_WEEPROM 7
#define CMD_CEEPROM 8
#define CMD_RESET 9
#define CMD_SET_STREAM 10
#define CMD_SET_ONETIME 11
#define CMD_SV_SETTINGS 12
#define CMD_RAM_SETTINGS 13
#define CMD_STOP_INTERP 14
#define CMD_GO_INTERP 15
void
usbFunctionWriteOut (uchar *data, uchar len)
{
  if (len <= 8)
    {
      cmd_len = len;
      memcpy (cmd_data, data, len);
    }
}

static jmp_buf jmpbuf_beginning;
/*
 * hadUsbReset - Reset handling
 *               Called after we see USB RESET
 *
 *             Called here, if usbPoll is called
 *              |
 * RESET   ____ v
 *      __|    |___
 *         ^
 *         |
 *        usbPoll detects RESET
 */
void
hadUsbReset (void)
{
  if (in_usb_init)
    return;

  longjmp (jmpbuf_beginning, 1);
}

static unsigned char vram_ptr = 0;

static void
vram_clr (void)
{ 
  memset (vram, 0, VRAM_MAX);
}

static void
vram_memcpy (const unsigned char *p, unsigned char len)
{
  unsigned char i;

  for (i = 0; i < len; i++)
    vram[(vram_ptr + i) & (VRAM_MAX - 1)] = *p++;

  vram_ptr += len;
}

static void
vram_drawchar (unsigned char c)
{
  unsigned char len = char_width (c) * 2;
  const unsigned char *p = char_bits (c); 
  unsigned char i;

  if (p == NULL)
    return;

  for (i = 0; i < len; i++)
    vram[(vram_ptr + i) & (VRAM_MAX - 1)] = pgm_read_byte(p++);

  vram_ptr += len;
}

static unsigned int eeprom_pos;

static void
eeprom_write_to (const unsigned char *p, unsigned char len)
{
  int i; /* ATmega328 has 1024 bits EEPROM, so char is too small */ 

  for(i = 0; i < len; i++)
    eeprom_write_byte ((unsigned char *) eeprom_pos++, *p++);

  return;
}

static void
eeprom_erase (unsigned char a)
{
  unsigned int i;
  unsigned int st, end;

  /*
   *  clearing the settings resets them to default vaules
   */
  switch (a)
    {
    /* clear all (default) */
    case 0:
      st = 0;
      end = EEPROM_SIZE;
      break;

    /* clear all but settings */
    case 1:
      st = 0;
      end = EEPROM_SIZE - EEPROM_SASIZE;
      break;

    /* clear only settings */
    case 2:
      st = EEPROM_SIZE - EEPROM_SASIZE;
      end = EEPROM_SIZE;
      break;
    }

  for(i = st; i < end; i++)
    eeprom_write_byte ((unsigned char *)i, 0);
  eeprom_pos = 0;

  return;
}

/*
 *  finds out how much eeprom is written 
 */
static unsigned char
eeprom_size (void)
{
  unsigned char size = 0;
  unsigned int i;

  unsigned int word;

  for(i = 0; i < EEPROM_SIZE - EEPROM_SASIZE; i++)
    {
      word = eeprom_read_byte ((unsigned char *)i);
      if (word)
	size = i;
    }

  return ++size;
}

#define TEXT_MAX 256
static unsigned char text[TEXT_MAX];
static unsigned char ram_txt_pos = 0;
static void save_text2ram (unsigned char *src, unsigned char len)
{
  if (ram_txt_pos + len >= TEXT_MAX)
    return;

  memcpy (&text[ram_txt_pos], src, len);
  ram_txt_pos += len;

  return;
}

#define INTERP_TASK_RUN		0x80  /* 1000 0000 */
#define INTERP_TASK_WAIT	0x40  /* 0100 0000 */
#define INTERP_TASK_MASK	0x07  /* 0000 0111 */
#define INTERP_MODE_ASCII   0
#define INTERP_MODE_BITMAP  1
#define INTERP_MODE_CHAR    2

#define INTERP_SCROLL_WAIT 3

#define INTERP_CTRL_RESET  '\0'
#define INTERP_CTRL_BITMAP '\x7F'

struct interp_context {
  unsigned char mode;
  unsigned int pc;
  unsigned char ch;
  unsigned char column;
  unsigned char column_max;
  unsigned char wait;
};

static void
update_vdp ()
{
  vram_disp_pos++;
  return;
}
static struct interp_context ic;

/* forward declarations */
static void read_eeprom_text (void);
static void read_eeprom_sett (void);

static void
interp_reset (void)
{
  read_eeprom_sett ();
  vram_disp_pos = 0;
  vram_ptr = DISP_COLUMNS*2;
  vram_clr ();
  ic.mode = INTERP_MODE_ASCII & INTERP_TASK_MASK;
  ic.pc = 0;
}

static void
interp_go (void)
{
  ic.mode |= INTERP_TASK_RUN;
}

static void
interp_stop (void)
{
  ic.mode &= ~INTERP_TASK_RUN;
}

static unsigned char scroll_wait;

/*
 * do_interp - Run interpreter
 */
static void
do_interp (void)
{
  if ((ic.mode & INTERP_TASK_RUN) == 0)
    return; /* interp_stop has been called */

  if ((ic.mode & INTERP_TASK_WAIT) != 0)
    {
      if (line)
	return;	/* a line is being processed now */


      if (--ic.wait == 0)
	ic.mode &= ~INTERP_TASK_WAIT;

      return;
    }

  if ((ic.mode & INTERP_TASK_MASK) == INTERP_MODE_ASCII)
    {
      unsigned char c = text[ic.pc++];

      if (c == INTERP_CTRL_RESET)
	ic.pc = 0;
      else if (c == INTERP_CTRL_BITMAP)
	{
	  ic.mode &= ~INTERP_TASK_MASK;
	  ic.mode |= INTERP_MODE_BITMAP | INTERP_TASK_RUN;
	  ic.column = 0;
	  ic.column_max = text[ic.pc++];
	}
      else
	{
	  ic.mode = (INTERP_MODE_CHAR & INTERP_TASK_MASK) | INTERP_TASK_RUN;
	  ic.ch = c;
	  ic.column = 0;
	  ic.column_max = char_width (c);
	}
    }
  else if ((ic.mode & INTERP_TASK_MASK) == INTERP_MODE_BITMAP)
    {
      vram[vram_ptr++ & (VRAM_MAX - 1)] = text[ic.pc++];
      vram[vram_ptr++ & (VRAM_MAX - 1)] = text[ic.pc++];
      update_vdp ();
      if (++ic.column >= ic.column_max)
	{
	  ic.mode &= ~INTERP_TASK_MASK;
	  ic.mode |= INTERP_MODE_ASCII | INTERP_TASK_RUN;
	}

      ic.wait = scroll_wait;
      ic.mode |= INTERP_TASK_WAIT;
    }
  else if ((ic.mode & INTERP_TASK_MASK) == INTERP_MODE_CHAR)
    {
      const unsigned char *bits;
      unsigned char col = ic.column++;

      bits = char_bits (ic.ch) + (col * 2);
      vram[vram_ptr++ & (VRAM_MAX - 1)] = pgm_read_byte(bits++);
      vram[vram_ptr++ & (VRAM_MAX - 1)] = pgm_read_byte(bits);
      update_vdp ();
      if (ic.column >= ic.column_max)
	{
	  ic.mode &= ~INTERP_TASK_MASK;
	  ic.mode |= INTERP_MODE_ASCII | INTERP_TASK_RUN;
	}

      ic.wait = scroll_wait;
      ic.mode |= INTERP_TASK_WAIT;
    }
}

/*
 * reading text/bitmap from EEPROM is default
 */
static void
read_eeprom_text(void)
{
  memset (text, 0, TEXT_MAX);
  size_t n = eeprom_size();

  if (n > TEXT_MAX)
    n = TEXT_MAX;
  eeprom_read_block (&text, 0, n);

  return;
}

static void
read_sett (unsigned char *settings)
{
  /*
   * The description of particular settings can be found in the header file
   * for the host side program: akiusb-host.h
   */

  /* VMODE */
  vmode = settings[0];

  /* brightness */
  /*
   * set_brightness (eep_settings[1]) is not used because it's better when
   * default setting (0) has default actions. Otherwise, on the def. setting
   * the pattern would not even be visible on the display which may confuse
   * first time users.
   */
  set_brightness (BRIGHTNESS_MAX - settings[1]);

  /* scroll speed */
  if (settings[2] == 0)
    scroll_wait = INTERP_SCROLL_WAIT;
  else
    scroll_wait = settings[2];

  /* blinking speed */
  if (settings[3] == 0)
    count_max = COUNT_MAX;
  else
    count_max = settings[3];

  return;
}

static void
read_eeprom_sett (void)
{
  unsigned char eep_settings[EEPROM_SASIZE];
  int i = EEPROM_SIZE - EEPROM_SASIZE;
  eeprom_read_block (eep_settings, (const void *)i, EEPROM_SASIZE);

  read_sett (eep_settings);

  return;
}


/*
 * do_usb - do the work for accepted USB packet
 *
 */
static void
do_usb (void)
{
  /*
   *  no need to mess around USB when it's disconnected from the bus
   */
  if (!usbConfiguration)
    return;

  usbPoll ();

  if (!cmd_len)
    return;

  switch (cmd_data[0])
    {
    case CMD_SETPTR:
      if (cmd_len == 2)
	if (cmd_data[1] < VRAM_MAX)
	  vram_ptr = cmd_data[1];
      break;

    case CMD_DATA:
      if (cmd_len >= 1)
	vram_memcpy (cmd_data+1, cmd_len - 1);
      break;

    case CMD_CLRSCR:
      if (cmd_len == 1)
	vram_clr ();
      break;

    case CMD_VMODE:
      if (cmd_len == 2
	  && (cmd_data[1] == VMODE_SAWTOOTH
	      || cmd_data[1] == VMODE_TRIANGLE
	      || cmd_data[1] == VMODE_BLINK))
	vmode = cmd_data[1];
      else if (cmd_len == 3
	       && cmd_data[1] == VMODE_STATIC
	       && cmd_data[2] <= BRIGHTNESS_MAX)
	{
	  vmode = cmd_data[1];
	  set_brightness (cmd_data[2]);
	}
      break;

    case CMD_CHAR:
      if (cmd_len == 2)
	vram_drawchar (cmd_data[1]);
      break;

    case CMD_SETDSP:
      if (cmd_len == 2)
	if (cmd_data[1] < VRAM_MAX)
	  vram_disp_pos = cmd_data[1];
      break;

    case CMD_SETCOL:
      if (cmd_len == 2)
	if (cmd_data[1] < VRAM_MAX/2)
	  vram_ptr = cmd_data[1]*2;
      break;

    case CMD_WEEPROM:
      if (cmd_len >= 1)
	eeprom_write_to (&cmd_data[1], cmd_len - 1);
      break;

    case CMD_CEEPROM:
      eeprom_erase (cmd_data[1]);
      break;

    /* 
     * faux-reset after changing text[] or settings
     */
    case CMD_RESET:
      read_eeprom_text ();
      read_eeprom_sett ();
      break;

    /* 
     * sets the program in streaming from USB mode
     */
    case CMD_SET_STREAM:
      break;

    /* 
     * receives text through USB to be kept in RAM
     */
    case CMD_SET_ONETIME:
      vram_clr ();
      /* use received text */
      if (cmd_len == 1)
	{
	  memset (&text[ram_txt_pos], 0, TEXT_MAX-ram_txt_pos);
	  ram_txt_pos = 0;
	  interp_reset ();
	  interp_go ();
	}
      else
	/* receive text and save it to text[] */
	save_text2ram (&cmd_data[1], cmd_len - 1);
      break;

    /* 
     * receives an array of settings which the ATmega will write to the last
     * bytes of its EEPROM.
     */
    case CMD_SV_SETTINGS:
      /* +1 is for command ID (CMD_SV_SETTINGS) */
      if (cmd_len == EEPROM_SASIZE + 1)
	{
	  --cmd_len;
	  eeprom_write_block (&cmd_data[1], \
			      (void *) (EEPROM_SIZE - cmd_len), cmd_len);
	}
      break;

    case CMD_STOP_INTERP:
      if (cmd_len == 1)
	interp_stop ();
      break;

    case CMD_RAM_SETTINGS:
      if (cmd_len == EEPROM_SASIZE +1)
	{
	  --cmd_len;
	  read_sett (&cmd_data[1]);
	}
      break;

    case CMD_GO_INTERP:
      if (cmd_len == 1)
	interp_go ();
      break;

    }

  cmd_len = 0;
}


/*
 * The main routine
 *
 * This program does three things:
 *   - Display the contents of VRAM in background (by timer_intr)
 *   - Run interpreter
 *   - Process USB "interrupt write transfer" (by V-USB firmware and do_usb)
 */
int
main (void)
{
  io_init ();
  sys_timer_init ();

  if (setjmp (jmpbuf_beginning) != 0)
    /* return from longjmp */;

  usb_init ();

  read_eeprom_text ();
  interp_reset ();
  interp_go ();

  while (1)
    {
      sys_sleep ();
      do_interp ();
      do_usb ();
    }
}
